package chainmaker

import (
	"crypto/x509"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"chainmaker.org/chainmaker/common/v2/cert"
	"chainmaker.org/chainmaker/common/v2/crypto"
	"chainmaker.org/chainmaker/common/v2/evmutils"

	bcx509 "chainmaker.org/chainmaker/common/v2/crypto/x509"
)

// 生成用户 crt
func (cm *ChainMaker) GenerateUserCrt(name string) (signCrt, signKey, tlsCrt, tlsKey string, err error) {
	orgName := fmt.Sprintf("%s.%s", cm.cryptoGenConfig.HostName, cm.cryptoGenConfig.Domain) // TODO:正式环境需要调换为该方式
	// orgName := fmt.Sprintf("%s%d.%s", cm.cryptoGenConfig.HostName, 1, cm.cryptoGenConfig.Domain)
	outputDir := "/home/zero-demo-rpc/UserCerts"

	user := cm.cryptoGenConfig.User
	sans := cm.cryptoGenConfig.SANS
	userPath := filepath.Join(outputDir, orgName, "user")
	keyType := crypto.AsymAlgoMap[strings.ToUpper(cm.cryptoGenConfig.PKAlgo)]
	hashType := crypto.HashAlgoMap[strings.ToUpper(cm.cryptoGenConfig.SKIHash)]
	return generateUser(
		userPath,
		name,
		cm.caKeyPath(),
		cm.caCrtPath(),
		user.Location.Country,
		user.Location.Locality,
		user.Location.Province,
		orgName,
		user.Type,
		user.ExpireYear,
		keyType,
		hashType,
		sans,
	)
}

func BuildUserCrt(args ...string) string {
	return strings.Join(args, ":::")
}

func ParseUserCrt(cert string) (signCrt, signKey, tlsCrt, tlsKey string, err error) {
	arr := strings.Split(cert, ":::")
	if len(arr) != 4 {
		return "", "", "", "", errors.New("ParseUserCrt failed, cert format err")
	}
	return arr[0], arr[1], arr[2], arr[3], nil
}

func GenerateUserName(prefix string, userId int64, phone string) string {
	encryptedUserId := userId + 66
	return fmt.Sprintf("%s%d%s", prefix, encryptedUserId, phoneSuffix(phone))
}

func phoneSuffix(phone string) string {
	if len(phone) < 4 {
		return ""
	}
	return phone[len(phone)-4:]
}

// 通过证书生成用户地址
func (cm *ChainMaker) MakeAddrAndSkiFromCrt(signCrtBytes []byte) (clientAddrInt, clientEthAddr, clientAddrSki string, err error) {
	blockCrt, _ := pem.Decode(signCrtBytes)
	crt, err := bcx509.ParseCertificate(blockCrt.Bytes)
	if err != nil {
		return "", "", "", err
	}

	ski := hex.EncodeToString(crt.SubjectKeyId)
	addrInt, err := evmutils.MakeAddressFromHex(ski)
	if err != nil {
		return "", "", "", err
	}

	return addrInt.String(), fmt.Sprintf("0x%x", addrInt.AsStringKey()), ski, nil
}

func generateUser(path, name, caKeyPath, caCertPath string,
	c, l, p, org, ou string, expireYear int32,
	keyType crypto.KeyType, hashType crypto.HashType, sans []string) (signCrt, signKey, tlsCrt, tlsKey string, err error) {
	signName := fmt.Sprintf("%s.sign", name)
	signCN := fmt.Sprintf("%s.%s", signName, org)
	tlsName := fmt.Sprintf("%s.tls", name)
	tlsCN := fmt.Sprintf("%s.%s", tlsName, org)

	signFile := filepath.Join(path, signName)
	signKeyName := fmt.Sprintf("%s.key", signFile)
	signCrtName := fmt.Sprintf("%s.crt", signFile)

	tlsFile := filepath.Join(path, tlsName)
	tlsKeyName := fmt.Sprintf("%s.key", tlsFile)
	tlsCrtName := fmt.Sprintf("%s.crt", tlsFile)

	// 删除文件
	// defer func() {
	// 	os.Remove(signKeyName)
	// 	os.Remove(signCrtName)
	// 	os.Remove(tlsKeyName)
	// 	os.Remove(tlsCrtName)
	// }()

	if err := generatePairs(
		path, caKeyPath, caCertPath,
		c, l, p, org, ou, signCN,
		signName,
		expireYear,
		sans,
		"",
		keyType,
		hashType,
		keyPairTypeUserSign,
	); err != nil {
		return "", "", "", "", err
	}

	if err := generatePairs(
		path, caKeyPath, caCertPath,
		c, l, p, org, ou, tlsCN,
		tlsName,
		expireYear,
		sans,
		"",
		keyType,
		hashType,
		keyPairTypeUserTLS,
	); err != nil {
		return "", "", "", "", err
	}

	// 生成文件完成后，读取文件
	signKeyC, err := os.ReadFile(signKeyName)
	if err != nil {
		return "", "", "", "", err
	}
	signCrtC, err := os.ReadFile(signCrtName)
	if err != nil {
		return "", "", "", "", err
	}
	tlsKeyC, err := os.ReadFile(tlsKeyName)
	if err != nil {
		return "", "", "", "", err
	}
	tlsCrtC, err := os.ReadFile(tlsCrtName)
	if err != nil {
		return "", "", "", "", err
	}
	return string(signCrtC), string(signKeyC), string(tlsCrtC), string(tlsKeyC), nil
}

type keyPairType uint8

const (
	keyPairTypeUserSign = 0
	keyPairTypeUserTLS  = 1
	keyPairTypeNodeSign = 2
	keyPairTypeNodeTLS  = 3
)

func generatePairs(path, caKeyPath, caCertPath, c, l, p, org, ou, cn, name string, expireYear int32,
	sans []string, uuid string, keyType crypto.KeyType, hashType crypto.HashType, pairType keyPairType) error {

	keyName := fmt.Sprintf("%s.key", name)
	csrName := fmt.Sprintf("%s.csr", name)
	certName := fmt.Sprintf("%s.crt", name)
	csrPath := filepath.Join(path, csrName)
	defer os.Remove(csrPath)

	var keyUsages []x509.KeyUsage
	var extKeyUsages []x509.ExtKeyUsage

	var isTls bool = false
	switch pairType {
	case keyPairTypeUserSign, keyPairTypeNodeSign:
		keyUsages = []x509.KeyUsage{x509.KeyUsageDigitalSignature, x509.KeyUsageContentCommitment}
	case keyPairTypeUserTLS:
		keyUsages = []x509.KeyUsage{
			x509.KeyUsageKeyEncipherment,
			x509.KeyUsageDataEncipherment,
			x509.KeyUsageKeyAgreement,
			x509.KeyUsageDigitalSignature,
			x509.KeyUsageContentCommitment,
		}
		extKeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
		isTls = true
	case keyPairTypeNodeTLS:
		keyUsages = []x509.KeyUsage{
			x509.KeyUsageKeyEncipherment,
			x509.KeyUsageDataEncipherment,
			x509.KeyUsageKeyAgreement,
			x509.KeyUsageDigitalSignature,
			x509.KeyUsageContentCommitment,
		}
		extKeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
		isTls = true
	}

	privKey, err := cert.CreatePrivKey(keyType, path, keyName, isTls)
	if err != nil {
		return err
	}

	if err := cert.CreateCSR(
		&cert.CSRConfig{
			PrivKey:            privKey,
			CsrPath:            path,
			CsrFileName:        csrName,
			Country:            c,
			Locality:           l,
			Province:           p,
			OrganizationalUnit: ou,
			Organization:       org,
			CommonName:         cn,
		},
	); err != nil {
		return err
	}

	if err := cert.IssueCertificate(
		&cert.IssueCertificateConfig{
			HashType:              hashType,
			IssuerPrivKeyFilePath: caKeyPath,
			IssuerCertFilePath:    caCertPath,
			CsrFilePath:           csrPath,
			CertPath:              path, CertFileName: certName,
			ExpireYear: expireYear,
			Sans:       sans,
			//			Uuid:         uuid,
			KeyUsages:    keyUsages,
			ExtKeyUsages: extKeyUsages,
		},
	); err != nil {
		return err
	}

	return nil
}

// parseCert convert bytearray to certificate
func parseCert(crtPEM []byte) (*bcx509.Certificate, error) {
	certBlock, _ := pem.Decode(crtPEM)
	if certBlock == nil {
		return nil, fmt.Errorf("decode pem failed, invalid certificate")
	}

	cert, err := bcx509.ParseCertificate(certBlock.Bytes)
	if err != nil {
		return nil, fmt.Errorf("x509 parse cert failed, %s", err)
	}

	return cert, nil
}
